package hashTbl

import (
	"json"
	"storageproto"
)

//struct to hold the hashtable (map) and it's lock
//The hashTable is held inside a 1-capacity channel, each function pulls the map out of the channel
//at the start (unlock) and puts it back in at the end.
type HashTable struct {
	mapChan chan (map[string] string)
}

//workaround to unmarshal a list of strings so you don't need to know the length of the array
type StrList struct {
	List []string
}

//construct a new hashtable
func NewHashTable() *HashTable {
	ht := &HashTable{make(chan map[string] string,1)}
	mp := make(map[string] string)
	ht.mapChan <- mp
	return ht
}

//retrieve a value from the hashtable
func (tbl *HashTable) GET(key string) (string, int){
	mp := <- tbl.mapChan
	val, ok := mp[key]
	tbl.mapChan <- mp
	if !ok {
		return "", storageproto.EKEYNOTFOUND
	}
	return val, storageproto.OK
}

//insert/modify a value in the hashtable
func (tbl *HashTable) PUT(key string, val string) int {
	mp := <- tbl.mapChan
	mp[key] = val
	tbl.mapChan <- mp
	return storageproto.OK
}

//insert/modify a whole list in the hashtable
func (tbl *HashTable) PUTLIST(key string, val []string) int {
	mp := <- tbl.mapChan
	sl := StrList{val}
	rawlist, _ := json.Marshal(sl)
	mp[key] = string(rawlist)
	tbl.mapChan <- mp
	return storageproto.OK
}

//retrieve a list from the hashtable
func (tbl *HashTable) GETLIST(key string) ([]string, int){
	mp := <- tbl.mapChan
	rawList, ok := mp[key]
	var sl StrList
	if ok {
		json.Unmarshal([]byte(rawList), &sl)
	}
	tbl.mapChan <- mp
	if !ok {
		return nil, storageproto.EKEYNOTFOUND
	}
	return sl.List, storageproto.OK
}

//add a value to an existing list in the hashtable, or create a list with a given value
func (tbl *HashTable) AddToList(key string, val string) int {
	mp := <- tbl.mapChan
	rawList, ok := mp[key]
	var sl StrList
	if ok { //if there was an element there, add a value to the list
		err := json.Unmarshal([]byte(rawList), &sl)
		if err == nil {
			//search through the list
			for idx := range sl.List {
				if sl.List[idx] == val{
					tbl.mapChan <- mp
					return storageproto.EITEMEXISTS
				}
			}
			sl.List = append(sl.List, val)
			rawList, _ := json.Marshal(sl)
			mp[key] = string(rawList)
		} else {
			tbl.mapChan <- mp
			return storageproto.EPUTFAILED
		}
	} else { //if there was no value, create a list with the given element
		sl.List = make([]string,1,100)
		sl.List[0] = val
		rawList, _ := json.Marshal(sl)
		mp[key] = string(rawList)
	}
	tbl.mapChan <- mp
	return storageproto.OK
}

//remove a value from a given list
func (tbl *HashTable) RemoveFromList(key string, val string) int {
	mp := <- tbl.mapChan
	rawList, ok := mp[key]
	var sl StrList
	found := false
	if ok {
		json.Unmarshal([]byte(rawList), &sl)
		//search through the list
		for idx := 0; idx < len(sl.List); idx++ {
			if sl.List[idx] == val {
				sl.List[idx] = sl.List[len(sl.List) - 1]
				found = true
				break
			}
		}
		//if the element was found, reduce the length of the list by 1
		if found {
			sl.List = sl.List[0:len(sl.List)-1]
			rawList, _ := json.Marshal(sl)
			mp[key] = string(rawList)
		} else {
			//put the list back in the hashtable
			mp[key] = string(rawList)
		}
	}
	tbl.mapChan <- mp
	if found {
		return storageproto.OK
	}
	return storageproto.EITEMNOTFOUND
}
